package io.github.frailty9.crserver.dso.service.impl;

import com.mybatisflex.kotlin.extensions.db.baseMapper
import com.mybatisflex.kotlin.extensions.db.insert
import com.mybatisflex.kotlin.extensions.db.query
import com.mybatisflex.kotlin.extensions.db.queryOne
import com.mybatisflex.kotlin.extensions.db.queryOneAs
import com.mybatisflex.kotlin.extensions.db.update
import com.mybatisflex.kotlin.extensions.kproperty.eq
import io.github.frailty9.crserver.model.Account;
import io.github.frailty9.crserver.dso.service.AccountService;
import io.github.frailty9.crserver.dto.AccountInfo
import io.github.frailty9.crserver.dto.RegisterDto
import io.github.frailty9.crserver.exception.DataInconsistencyException
import io.github.frailty9.crserver.model.User
import mu.KotlinLogging
import org.mindrot.jbcrypt.BCrypt
import org.noear.solon.annotation.Component;
import org.noear.solon.data.annotation.Transaction

/**
 * AccountServiceImpl
 */
@Component
open class AccountServiceImpl : AccountService {

    val log = KotlinLogging.logger { }

    @Transaction(readOnly = true)
    override fun isUsernameExists(username: String): Boolean {
        return queryOneAs<Account, Long> {
            select("1")
            where(Account::username eq username)
        } != null
    }

    @Transaction
    override fun register(registerDto: RegisterDto) {
        // 加密密码
        val hashPwd = BCrypt.hashpw(registerDto.password, BCrypt.gensalt())
        val account = Account(
            username = registerDto.username,
            password = hashPwd,
            role = registerDto.role,
        )

        insert(account)

        // 获取全局唯一id, 并注册用户信息
        val userId = account.id!!
        val user = registerDto.userInfo.apply {
            id = userId
        }
        insert(user)
    }

    @Transaction(readOnly = true)
    override fun login(username: String, password: String): Long {
        // 查询密码
        val account = queryOne<Account> {
            select(Account::id, Account::password)
            where(Account::username eq username)
        } ?: return 0
        // 验证密码
        return if (BCrypt.checkpw(password, account.password)) {
            account.id!!
        } else {
            0
        }
    }

    @Transaction
    override fun changePassword(uid: Long, oldPassword: String, newPassword: String): Boolean {
        // 查询密码
        val account = queryOne<Account> {
            select(Account::id, Account::password, Account::version)
            where(Account::id eq uid)
        } ?: throw DataInconsistencyException("修改密码时查询为空, uid: $uid")
        // 验证密码
        if (BCrypt.checkpw(oldPassword, account.password)) {
            // 加密密码
            val hashNewPwd = BCrypt.hashpw(newPassword, BCrypt.gensalt())
            // 更新密码
            account.password = hashNewPwd
            update<Account> {
                Account::password set hashNewPwd
                Account::id eq uid
            }

            return true
        } else {
            return false
        }
    }

    @Transaction(readOnly = true)
    override fun getRole(id: Long): String {
        val account = queryOne<Account> {
            select(Account::role)
            where(Account::id eq id)
        }
        return account?.role ?: throw DataInconsistencyException("查询Account.role为空, id: $id")
    }

    @Transaction(readOnly = true)
    override fun getAll(): List<AccountInfo> {
        // ISSUE: 手写XML提升查询效率
        val accountMap = mutableMapOf<Long, Account>()
        query<Account> {}.forEach { accountMap[it.id!!] = it }
        val result = query<User> {}.map {
            AccountInfo(
                user = it,
                account = accountMap[it.id]
            )
        }
        return result
    }

    @Transaction
    override fun resetPassword(userId: Long, defaultHashedPassword: String) {
        val account = queryOne<Account> {
            select(Account::id, Account::password, Account::version)
            Account::id eq userId
        }!!.apply {
            password = defaultHashedPassword
        }
        Account::class.baseMapper.update(account, true)
    }
}
